package com.tools.web.interceptor.access.own;

import com.tools.common.object.Note;
import com.tools.web.GlobalKit;
import com.tools.web.interceptor.access.AccessManager;
import com.tools.web.interceptor.access.AccessSource;
import com.tools.web.interceptor.access.OwnCron;
import com.tools.web.interceptor.auth.AuthUser;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;

import java.lang.annotation.Annotation;
import java.util.Map;

/**
 * ”注解方式实现的“ 接口访问的角色权限校验拦截器
 * */
@Note("”注解方式实现的“ 接口访问的角色权限校验拦截器")
public final class DefaultAccessManager implements AccessManager {

    @Note("没有角色/权限访问时返回的错误信息，如果为空则会抛 ServiceException")
    private final String accessFailErrorMessage;

    @Note("若开启了时间范围验证，且接口的开放时间还没到，就响应该错误信息。如果为空则会抛 ServiceException")
    private final String unreleasedErrorMessage;

    /* *****************************************************************************************
     *
     *      构造器
     *
     * *****************************************************************************************
     * */

    public DefaultAccessManager(AccessSource source) {
        if(source == null) throw new NullPointerException("权限验证拦截器所需的参数配置项包装类不能为 null");
        this.accessFailErrorMessage = source.getAccessFailErrorMessage();
        this.unreleasedErrorMessage = source.getUnreleasedErrorMessage();
    }

    /* *****************************************************************************************
     *
     *      拦截逻辑
     *
     * *****************************************************************************************
     * */

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return this.preAuthorize(request, response, handler);
    }


    @Note("核心拦截逻辑")
    @Override
    public boolean preAuthorize(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(handler instanceof ResourceHttpRequestHandler) return true;
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Class<?> typeClass = handlerMethod.getBeanType();
        EnableAccess eaFromClass = typeClass.getAnnotation(EnableAccess.class);
        if(eaFromClass == null) {
            EnableAccess eaFromMethod = handlerMethod.getMethodAnnotation(EnableAccess.class);
            if(eaFromMethod == null) {
                return true;
            }
        }
        //检查方法上是否有注解配置
        Boolean methodCheck = this.private_checkAccess(handlerMethod, request, response);
        if(methodCheck != null) {
            response.getWriter().write(this.accessFailErrorMessage);
            return false;
        }
        Boolean classCheck = this.private_checkAccess(typeClass, request, response);
        if(classCheck != null) {
            response.getWriter().write(this.accessFailErrorMessage);
            return false;
        }
        return true;
    }

    /* ********************************************************************************************
     *
     *          私有逻辑
     *
     * *********************************************************************************************
     * */

    @Note("获取注解实例")
    private <T extends Annotation> T private_getAno(Object methodOrClass, Class<T> aClass) {
        if(methodOrClass instanceof HandlerMethod) {
            return ((HandlerMethod) methodOrClass).getMethodAnnotation(aClass);
        }
        return ((Class<?>) methodOrClass).getAnnotation(aClass);
    }


    @Note("校验是否开启时间验证，若开启则当前时间是否大于等于接口开放的时间，且小于接口关闭的时间。" +
            "若接口关闭时间没有声明，则接口开放后一直有效了")
    private boolean private_checkIsOpenDateTime(CronAccess a) {
        //为 null 则没有配置，直接放行下一步
        if(a == null) return true;
        String startCron = a.start();
        String endCron = a.end();
        if(!startCron.isEmpty() && !new OwnCron(startCron).gteNow()) {
            return false;
        }
        if(endCron.isEmpty()) return true;
        return new OwnCron(endCron).ltNow();
    }


    @Note("idSet 必须拥有 ids 里的所有 id 才返回 true")
    private boolean private_hasAllCheck(String[] ids, Map<String, ?> idMap) {
        for(String id : ids) {
            if(!idMap.containsKey(id)) return false;
        }
        return true;
    }

    @Note("在 idSet 里存在任意一个 ids 里的 id 就返回 true")
    private boolean private_hasAnyCheck(String[] ids, Map<String, ?> idMap) {
        for(String id : ids) {
            if(idMap.containsKey(id)) return true;
        }
        return false;
    }


    @Note("核心校验逻辑")
    private Boolean private_checkAccess(Object methodOrClass, HttpServletRequest request, HttpServletResponse response) throws Exception {
        AuthUser authUser = GlobalKit.getAuthUser(request);
        //校验是否有开放时间配置
        CronAccess c = this.private_getAno(methodOrClass, CronAccess.class);
        if(!this.private_checkIsOpenDateTime(c)) {
            response.getWriter().write(this.unreleasedErrorMessage);
            return false;
        }
        Boolean[] res = new Boolean[4];
        Boolean hasTrue = null;
        HasAllPerm allP = this.private_getAno(methodOrClass, HasAllPerm.class);
        if(allP != null) {
            res[0] = this.private_hasAllCheck(allP.value(), authUser.getPermKeyMap());
            hasTrue = true;
        }
        HasAnyPerm anyP = this.private_getAno(methodOrClass, HasAnyPerm.class);
        if(anyP != null) {
            res[1] = this.private_hasAnyCheck(anyP.value(), authUser.getPermKeyMap());
            hasTrue = true;
        }
        HasAllRole allR = this.private_getAno(methodOrClass, HasAllRole.class);
        if(allR != null) {
            res[2] = this.private_hasAllCheck(allR.value(), authUser.getRoleKeyMap());
            hasTrue = true;
        }
        HasAnyRole anyR = this.private_getAno(methodOrClass, HasAnyRole.class);
        if(anyR != null) {
            res[3] = this.private_hasAnyCheck(anyR.value(), authUser.getRoleKeyMap());
            hasTrue = true;
        }
        if(hasTrue == null) {
            return null;
        }
        AnyCheck anyCheck = this.private_getAno(methodOrClass, AnyCheck.class);
        //“或” 校验
        if(anyCheck == null) {
            for (Boolean b : res) {
                if(b != null && !b) return false;
            }
            return true;
        }
        //“与” 校验
        for (Boolean b : res) {
            if(b != null && b) return true;
        }
        return false;
    }



}
